/**
 * @license Highstock JS v7.2.0 (2019-09-03)
 *
 * Indicator series type for Highstock
 *
 * (c) 2010-2019 Sebastian Bochan
 *
 * License: www.highcharts.com/license
 */
'use strict';
(function (factory) {
    if (typeof module === 'object' && module.exports) {
        factory['default'] = factory;
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define('highcharts/indicators/ichimoku-kinko-hyo', ['highcharts', 'highcharts/modules/stock'], function (Highcharts) {
            factory(Highcharts);
            factory.Highcharts = Highcharts;
            return factory;
        });
    } else {
        factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
    }
}(function (Highcharts) {
    var _modules = Highcharts ? Highcharts._modules : {};
    function _registerModule(obj, path, args, fn) {
        if (!obj.hasOwnProperty(path)) {
            obj[path] = fn.apply(null, args);
        }
    }
    _registerModule(_modules, 'indicators/ichimoku-kinko-hyo.src.js', [_modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (H, U) {
        /* *
         *
         *  License: www.highcharts.com/license
         *
         * */



        var defined = U.defined,
            isArray = U.isArray,
            objectEach = U.objectEach;

        var UNDEFINED,
            seriesType = H.seriesType,
            merge = H.merge,
            color = H.color,
            SMA = H.seriesTypes.sma;

        // Utils:
        function maxHigh(arr) {
            return arr.reduce(function (max, res) {
                return Math.max(max, res[1]);
            }, -Infinity);
        }

        function minLow(arr) {
            return arr.reduce(function (min, res) {
                return Math.min(min, res[2]);
            }, Infinity);
        }

        function highlowLevel(arr) {
            return {
                high: maxHigh(arr),
                low: minLow(arr)
            };
        }

        function getClosestPointRange(axis) {
            var closestDataRange,
                loopLength,
                distance,
                xData,
                i;

            axis.series.forEach(function (series) {

                if (series.xData) {
                    xData = series.xData;
                    loopLength = series.xIncrement ? 1 : xData.length - 1;

                    for (i = loopLength; i > 0; i--) {
                        distance = xData[i] - xData[i - 1];
                        if (
                            closestDataRange === UNDEFINED ||
                            distance < closestDataRange
                        ) {
                            closestDataRange = distance;
                        }
                    }
                }
            });

            return closestDataRange;
        }

        // Check two lines intersection (line a1-a2 and b1-b2)
        // Source: https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
        function checkLineIntersection(a1, a2, b1, b2) {
            if (a1 && a2 && b1 && b2) {

                var saX = a2.plotX - a1.plotX, // Auxiliary section a2-a1 X
                    saY = a2.plotY - a1.plotY, // Auxiliary section a2-a1 Y
                    sbX = b2.plotX - b1.plotX, // Auxiliary section b2-b1 X
                    sbY = b2.plotY - b1.plotY, // Auxiliary section b2-b1 Y
                    sabX = a1.plotX - b1.plotX, // Auxiliary section a1-b1 X
                    sabY = a1.plotY - b1.plotY, // Auxiliary section a1-b1 Y

                    // First degree Bézier parameters
                    u,
                    t;

                u = (-saY * sabX + saX * sabY) / (-sbX * saY + saX * sbY);
                t = (sbX * sabY - sbY * sabX) / (-sbX * saY + saX * sbY);

                if (u >= 0 && u <= 1 && t >= 0 && t <= 1) {
                    return {
                        plotX: a1.plotX + (t * saX),
                        plotY: a1.plotY + (t * saY)
                    };
                }
            }

            return false;
        }

        // Parameter opt (indicator options object) include indicator, points,
        // nextPoints, color, options, gappedExtend and graph properties
        function drawSenkouSpan(opt) {
            var indicator = opt.indicator;

            indicator.points = opt.points;
            indicator.nextPoints = opt.nextPoints;
            indicator.color = opt.color;
            indicator.options = merge(opt.options.senkouSpan.styles, opt.gap);
            indicator.graph = opt.graph;
            indicator.fillGraph = true;
            SMA.prototype.drawGraph.call(indicator);
        }


        // Data integrity in Ichimoku is different than default "averages":
        // Point: [undefined, value, value, ...] is correct
        // Point: [undefined, undefined, undefined, ...] is incorrect
        H.approximations['ichimoku-averages'] = function () {
            var ret = [],
                isEmptyRange;

            [].forEach.call(arguments, function (arr, i) {
                ret.push(H.approximations.average(arr));
                isEmptyRange = !isEmptyRange && ret[i] === undefined;
            });

            // Return undefined when first elem. is undefined and let
            // sum method handle null (#7377)
            return isEmptyRange ? undefined : ret;
        };

        /**
         * The IKH series type.
         *
         * @private
         * @class
         * @name Highcharts.seriesTypes.ikh
         *
         * @augments Highcharts.Series
         */
        seriesType(
            'ikh',
            'sma',
            /**
             * Ichimoku Kinko Hyo (IKH). This series requires `linkedTo` option to be
             * set.
             *
             * @sample stock/indicators/ichimoku-kinko-hyo
             *         Ichimoku Kinko Hyo indicator
             *
             * @extends      plotOptions.sma
             * @since        6.0.0
             * @excluding    allAreas, colorAxis, compare, compareBase, joinBy, keys,
             *               navigatorOptions, pointInterval, pointIntervalUnit,
             *               pointPlacement, pointRange, pointStart, showInNavigator,
             *               stacking
             * @product      highstock
             * @optionparent plotOptions.ikh
             */
            {
                params: {
                    period: 26,
                    /**
                     * The base period for Tenkan calculations.
                     */
                    periodTenkan: 9,
                    /**
                     * The base period for Senkou Span B calculations
                     */
                    periodSenkouSpanB: 52
                },
                marker: {
                    enabled: false
                },
                tooltip: {
                    pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' +
                        'TENKAN SEN: {point.tenkanSen:.3f}<br/>' +
                        'KIJUN SEN: {point.kijunSen:.3f}<br/>' +
                        'CHIKOU SPAN: {point.chikouSpan:.3f}<br/>' +
                        'SENKOU SPAN A: {point.senkouSpanA:.3f}<br/>' +
                        'SENKOU SPAN B: {point.senkouSpanB:.3f}<br/>'
                },
                /**
                 * The styles for Tenkan line
                 */
                tenkanLine: {
                    styles: {
                        /**
                         * Pixel width of the line.
                         */
                        lineWidth: 1,
                        /**
                         * Color of the line.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        lineColor: undefined
                    }
                },
                /**
                 * The styles for Kijun line
                 */
                kijunLine: {
                    styles: {
                        /**
                         * Pixel width of the line.
                         */
                        lineWidth: 1,
                        /**
                         * Color of the line.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        lineColor: undefined
                    }
                },
                /**
                 * The styles for Chikou line
                 */
                chikouLine: {
                    styles: {
                        /**
                         * Pixel width of the line.
                         */
                        lineWidth: 1,
                        /**
                         * Color of the line.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        lineColor: undefined
                    }
                },
                /**
                 * The styles for Senkou Span A line
                 */
                senkouSpanA: {
                    styles: {
                        /**
                         * Pixel width of the line.
                         */
                        lineWidth: 1,
                        /**
                         * Color of the line.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        lineColor: undefined
                    }
                },
                /**
                 * The styles for Senkou Span B line
                 */
                senkouSpanB: {
                    styles: {
                        /**
                         * Pixel width of the line.
                         */
                        lineWidth: 1,
                        /**
                         * Color of the line.
                         *
                         * @type {Highcharts.ColorString}
                         */
                        lineColor: undefined
                    }
                },
                /**
                 * The styles for area between Senkou Span A and B.
                 */
                senkouSpan: {
                    /**
                    * Color of the area between Senkou Span A and B,
                    * when Senkou Span A is above Senkou Span B. Note that if
                    * a `style.fill` is defined, the `color` takes precedence and
                    * the `style.fill` is ignored.
                    *
                    * @see [senkouSpan.styles.fill](#series.ikh.senkouSpan.styles.fill)
                    *
                    * @sample stock/indicators/ichimoku-kinko-hyo
                    *         Ichimoku Kinko Hyo color
                    *
                    * @type      {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
                    * @since     7.0.0
                    * @apioption plotOptions.ikh.senkouSpan.color
                    */

                    /**
                    * Color of the area between Senkou Span A and B,
                    * when Senkou Span A is under Senkou Span B.
                    *
                    * @sample stock/indicators/ikh-negative-color
                    *         Ichimoku Kinko Hyo negativeColor
                    *
                    * @type      {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
                    * @since     7.0.0
                    * @apioption plotOptions.ikh.senkouSpan.negativeColor
                    */

                    styles: {
                        /**
                         * Color of the area between Senkou Span A and B.
                         *
                         * @deprecated
                         * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
                         */
                        fill: 'rgba(255, 0, 0, 0.5)'
                    }
                },
                dataGrouping: {
                    approximation: 'ichimoku-averages'
                }
            },
            /**
             * @lends Highcharts.Series#
             */
            {
                pointArrayMap: [
                    'tenkanSen',
                    'kijunSen',
                    'chikouSpan',
                    'senkouSpanA',
                    'senkouSpanB'
                ],
                pointValKey: 'tenkanSen',
                nameComponents: ['periodSenkouSpanB', 'period', 'periodTenkan'],
                init: function () {
                    SMA.prototype.init.apply(this, arguments);

                    // Set default color for lines:
                    this.options = merge({
                        tenkanLine: {
                            styles: {
                                lineColor: this.color
                            }
                        },
                        kijunLine: {
                            styles: {
                                lineColor: this.color
                            }
                        },
                        chikouLine: {
                            styles: {
                                lineColor: this.color
                            }
                        },
                        senkouSpanA: {
                            styles: {
                                lineColor: this.color,
                                fill: color(this.color).setOpacity(0.5).get()
                            }
                        },
                        senkouSpanB: {
                            styles: {
                                lineColor: this.color,
                                fill: color(this.color).setOpacity(0.5).get()
                            }
                        },
                        senkouSpan: {
                            styles: {
                                fill: color(this.color).setOpacity(0.2).get()
                            }
                        }
                    }, this.options);
                },
                toYData: function (point) {
                    return [
                        point.tenkanSen,
                        point.kijunSen,
                        point.chikouSpan,
                        point.senkouSpanA,
                        point.senkouSpanB
                    ];
                },
                translate: function () {
                    var indicator = this;

                    SMA.prototype.translate.apply(indicator);

                    indicator.points.forEach(function (point) {
                        indicator.pointArrayMap.forEach(function (value) {
                            if (defined(point[value])) {
                                point['plot' + value] = indicator.yAxis.toPixels(
                                    point[value],
                                    true
                                );

                                // Add extra parameters for support tooltip in moved
                                // lines
                                point.plotY = point['plot' + value];
                                point.tooltipPos = [point.plotX, point['plot' + value]];
                                point.isNull = false;
                            }
                        });
                    });
                },
                // One does not simply
                // Render five lines
                // And an arearange
                // In just one series..
                drawGraph: function () {

                    var indicator = this,
                        mainLinePoints = indicator.points,
                        pointsLength = mainLinePoints.length,
                        mainLineOptions = indicator.options,
                        mainLinePath = indicator.graph,
                        mainColor = indicator.color,
                        gappedExtend = {
                            options: {
                                gapSize: mainLineOptions.gapSize
                            }
                        },
                        pointArrayMapLength = indicator.pointArrayMap.length,
                        allIchimokuPoints = [[], [], [], [], [], []],
                        ikhMap = {
                            tenkanLine: allIchimokuPoints[0],
                            kijunLine: allIchimokuPoints[1],
                            chikouLine: allIchimokuPoints[2],
                            senkouSpanA: allIchimokuPoints[3],
                            senkouSpanB: allIchimokuPoints[4],
                            senkouSpan: allIchimokuPoints[5]
                        },
                        intersectIndexColl = [],
                        senkouSpanOptions = indicator.options.senkouSpan,
                        color = senkouSpanOptions.color ||
                            senkouSpanOptions.styles.fill,
                        negativeColor = senkouSpanOptions.negativeColor,

                        // Points to create color and negativeColor senkouSpan
                        points = [
                            [], // Points color
                            [] // Points negative color
                        ],
                        // For span, we need an access to the next points, used in
                        // getGraphPath()
                        nextPoints = [
                            [], // NextPoints color
                            [] // NextPoints negative color
                        ],
                        lineIndex = 0,
                        position,
                        point,
                        i,
                        startIntersect,
                        endIntersect,
                        sectionPoints,
                        sectionNextPoints,
                        pointsPlotYSum,
                        nextPointsPlotYSum,
                        senkouSpanTempColor,
                        concatArrIndex,
                        j,
                        k;

                    indicator.ikhMap = ikhMap;

                    // Generate points for all lines and spans lines:
                    while (pointsLength--) {
                        point = mainLinePoints[pointsLength];
                        for (i = 0; i < pointArrayMapLength; i++) {
                            position = indicator.pointArrayMap[i];

                            if (defined(point[position])) {
                                allIchimokuPoints[i].push({
                                    plotX: point.plotX,
                                    plotY: point['plot' + position],
                                    isNull: false
                                });
                            }
                        }

                        if (negativeColor &&
                            pointsLength !== mainLinePoints.length - 1) {
                            // Check if lines intersect
                            var index = ikhMap.senkouSpanB.length - 1,
                                intersect = checkLineIntersection(
                                    ikhMap.senkouSpanA[index - 1],
                                    ikhMap.senkouSpanA[index],
                                    ikhMap.senkouSpanB[index - 1],
                                    ikhMap.senkouSpanB[index]
                                ),
                                intersectPointObj = {
                                    plotX: intersect.plotX,
                                    plotY: intersect.plotY,
                                    isNull: false,
                                    intersectPoint: true
                                };

                            if (intersect) {
                                // Add intersect point to ichimoku points collection
                                // Create senkouSpan sections
                                ikhMap.senkouSpanA.splice(index, 0, intersectPointObj);
                                ikhMap.senkouSpanB.splice(index, 0, intersectPointObj);
                                intersectIndexColl.push(index);
                            }
                        }
                    }

                    // Modify options and generate lines:
                    objectEach(ikhMap, function (values, lineName) {
                        if (mainLineOptions[lineName] && lineName !== 'senkouSpan') {
                            // First line is rendered by default option
                            indicator.points = allIchimokuPoints[lineIndex];
                            indicator.options = merge(
                                mainLineOptions[lineName].styles,
                                gappedExtend
                            );
                            indicator.graph = indicator['graph' + lineName];

                            indicator.fillGraph = false;
                            indicator.color = mainColor;
                            SMA.prototype.drawGraph.call(indicator);

                            // Now save line
                            indicator['graph' + lineName] = indicator.graph;
                        }

                        lineIndex++;
                    });

                    // Generate senkouSpan area:

                    // If graphColection exist then remove svg
                    // element and indicator property
                    if (indicator.graphCollection) {
                        indicator.graphCollection.forEach(function (graphName) {
                            indicator[graphName].destroy();
                            delete indicator[graphName];
                        });
                    }

                    // Clean grapCollection or initialize it
                    indicator.graphCollection = [];

                    // When user set negativeColor property
                    if (
                        negativeColor &&
                        ikhMap.senkouSpanA[0] &&
                        ikhMap.senkouSpanB[0]) {

                        // Add first and last point to senkouSpan area sections
                        intersectIndexColl.unshift(0);
                        intersectIndexColl.push(ikhMap.senkouSpanA.length - 1);

                        // Populate points and nextPoints arrays
                        for (j = 0; j < intersectIndexColl.length - 1; j++) {
                            startIntersect = intersectIndexColl[j];
                            endIntersect = intersectIndexColl[j + 1];

                            sectionPoints = ikhMap.senkouSpanB.slice(
                                startIntersect, endIntersect + 1
                            );

                            sectionNextPoints = ikhMap.senkouSpanA.slice(
                                startIntersect, endIntersect + 1
                            );

                            // Add points to color or negativeColor arrays
                            // Check the middle point (if exist)
                            if (Math.floor(sectionPoints.length / 2) >= 1) {
                                var x = Math.floor(sectionPoints.length / 2);

                                // When middle points has equal values
                                // Compare all ponints plotY value sum
                                if (
                                    sectionPoints[x].plotY ===
                                    sectionNextPoints[x].plotY
                                ) {

                                    pointsPlotYSum = 0;
                                    nextPointsPlotYSum = 0;

                                    for (k = 0; k < sectionPoints.length; k++) {
                                        pointsPlotYSum +=
                                        sectionPoints[k].plotY;
                                        nextPointsPlotYSum +=
                                        sectionNextPoints[k].plotY;
                                    }

                                    concatArrIndex =
                                        pointsPlotYSum > nextPointsPlotYSum ? 0 : 1;

                                    points[concatArrIndex] =
                                        points[concatArrIndex].concat(sectionPoints);

                                    nextPoints[concatArrIndex] =
                                        nextPoints[concatArrIndex].concat(
                                            sectionNextPoints
                                        );

                                } else {
                                    // Compare middle point of the section
                                    concatArrIndex =
                                        sectionPoints[x].plotY >
                                        sectionNextPoints[x].plotY ?
                                            0 : 1;

                                    points[concatArrIndex] =
                                        points[concatArrIndex].concat(sectionPoints);

                                    nextPoints[concatArrIndex] =
                                        nextPoints[concatArrIndex].concat(
                                            sectionNextPoints
                                        );
                                }
                            } else {
                                // Compare first point of the section
                                concatArrIndex =
                                        sectionPoints[0].plotY >
                                        sectionNextPoints[0].plotY ?
                                            0 : 1;

                                points[concatArrIndex] =
                                    points[concatArrIndex].concat(sectionPoints);

                                nextPoints[concatArrIndex] =
                                    nextPoints[concatArrIndex].concat(
                                        sectionNextPoints
                                    );
                            }
                        }

                        // Render color and negativeColor paths
                        [
                            'graphsenkouSpanColor', 'graphsenkouSpanNegativeColor'
                        ].forEach(
                            function (areaName, i) {
                                if (points[i].length && nextPoints[i].length) {

                                    senkouSpanTempColor = (i === 0) ?
                                        color : negativeColor;

                                    drawSenkouSpan({
                                        indicator: indicator,
                                        points: points[i],
                                        nextPoints: nextPoints[i],
                                        color: senkouSpanTempColor,
                                        options: mainLineOptions,
                                        gap: gappedExtend,
                                        graph: indicator[areaName]
                                    });

                                    // Now save line
                                    indicator[areaName] = indicator.graph;
                                    indicator.graphCollection.push(areaName);
                                }
                            }
                        );

                    } else {
                        // When user set only senkouSpan style.fill property
                        drawSenkouSpan({
                            indicator: indicator,
                            points: ikhMap.senkouSpanB,
                            nextPoints: ikhMap.senkouSpanA,
                            color: color,
                            options: mainLineOptions,
                            gap: gappedExtend,
                            graph: indicator.graphsenkouSpan
                        });

                        // Now save line
                        indicator.graphsenkouSpan = indicator.graph;
                    }

                    // Clean temporary properties:
                    delete indicator.nextPoints;
                    delete indicator.fillGraph;

                    // Restore options and draw the Tenkan line:
                    indicator.points = mainLinePoints;
                    indicator.options = mainLineOptions;
                    indicator.graph = mainLinePath;
                },
                getGraphPath: function (points) {
                    var indicator = this,
                        path = [],
                        spanA,
                        fillArray = [],
                        spanAarr = [];

                    points = points || this.points;


                    // Render Senkou Span
                    if (indicator.fillGraph && indicator.nextPoints) {

                        spanA = SMA.prototype.getGraphPath.call(
                            indicator,
                            // Reverse points, so Senkou Span A will start from the end:
                            indicator.nextPoints
                        );

                        spanA[0] = 'L';

                        path = SMA.prototype.getGraphPath.call(
                            indicator,
                            points
                        );

                        spanAarr = spanA.slice(0, path.length);

                        for (var i = (spanAarr.length - 1); i > 0; i -= 3) {
                            fillArray.push(
                                spanAarr[i - 2],
                                spanAarr[i - 1],
                                spanAarr[i]
                            );
                        }
                        path = path.concat(fillArray);

                    } else {
                        path = SMA.prototype.getGraphPath.apply(indicator, arguments);
                    }

                    return path;
                },
                getValues: function (series, params) {

                    var period = params.period,
                        periodTenkan = params.periodTenkan,
                        periodSenkouSpanB = params.periodSenkouSpanB,
                        xVal = series.xData,
                        yVal = series.yData,
                        xAxis = series.xAxis,
                        yValLen = (yVal && yVal.length) || 0,
                        closestPointRange = getClosestPointRange(xAxis),
                        IKH = [],
                        xData = [],
                        dateStart,
                        date,
                        slicedTSY,
                        slicedKSY,
                        slicedSSBY,
                        pointTS,
                        pointKS,
                        pointSSB,
                        i,
                        TS,
                        KS,
                        CS,
                        SSA,
                        SSB;

                    // Ikh requires close value
                    if (
                        xVal.length <= period ||
                        !isArray(yVal[0]) ||
                        yVal[0].length !== 4
                    ) {
                        return false;
                    }


                    // Add timestamps at the beginning
                    dateStart = xVal[0] - (period * closestPointRange);

                    for (i = 0; i < period; i++) {
                        xData.push(dateStart + i * closestPointRange);
                    }

                    for (i = 0; i < yValLen; i++) {

                        // Tenkan Sen
                        if (i >= periodTenkan) {

                            slicedTSY = yVal.slice(i - periodTenkan, i);

                            pointTS = highlowLevel(slicedTSY);

                            TS = (pointTS.high + pointTS.low) / 2;
                        }

                        if (i >= period) {

                            slicedKSY = yVal.slice(i - period, i);

                            pointKS = highlowLevel(slicedKSY);

                            KS = (pointKS.high + pointKS.low) / 2;

                            SSA = (TS + KS) / 2;
                        }

                        if (i >= periodSenkouSpanB) {

                            slicedSSBY = yVal.slice(i - periodSenkouSpanB, i);

                            pointSSB = highlowLevel(slicedSSBY);

                            SSB = (pointSSB.high + pointSSB.low) / 2;
                        }

                        CS = yVal[i][3];

                        date = xVal[i];

                        if (IKH[i] === UNDEFINED) {
                            IKH[i] = [];
                        }

                        if (IKH[i + period] === UNDEFINED) {
                            IKH[i + period] = [];
                        }

                        IKH[i + period][0] = TS;
                        IKH[i + period][1] = KS;
                        IKH[i + period][2] = UNDEFINED;

                        IKH[i][2] = CS;

                        if (i <= period) {
                            IKH[i + period][3] = UNDEFINED;
                            IKH[i + period][4] = UNDEFINED;
                        }

                        if (IKH[i + 2 * period] === UNDEFINED) {
                            IKH[i + 2 * period] = [];
                        }

                        IKH[i + 2 * period][3] = SSA;
                        IKH[i + 2 * period][4] = SSB;

                        xData.push(date);

                    }

                    // Add timestamps for further points
                    for (i = 1; i <= period; i++) {
                        xData.push(date + i * closestPointRange);
                    }

                    return {
                        values: IKH,
                        xData: xData,
                        yData: IKH
                    };
                }
            }
        );

        /**
         * A `IKH` series. If the [type](#series.ikh.type) option is not
         * specified, it is inherited from [chart.type](#chart.type).
         *
         * @extends   series,plotOptions.ikh
         * @since     6.0.0
         * @product   highstock
         * @excluding dataParser, dataURL
         * @apioption series.ikh
         */

    });
    _registerModule(_modules, 'masters/indicators/ichimoku-kinko-hyo.src.js', [], function () {


    });
}));